<!DOCTYPE html>
<html lang="en">
<head>
<title>Tic Tac Toe</title>
<style type='text/css'>
  .Hidden {
    display:none;
  }
.LB {
  border-left: 1px solid black;
}
.RB {
  border-right: 1px solid black;
}
.TB {
  border-top: 1px solid black;
}
.BB {
  border-bottom: 1px solid black;
}
.CurrentUser {
  background-color:yellow;
}
</style>
<script type='text/javascript src='/Scripts/promise.js'></script>
<script type='text/javascript' src='/Scripts/jquery-1.7.min.js'></script>
<script type='text/javascript'>
// MessageHolder is used to wait for a message response.
var MessageHolder = function(messageid,method,result,reject) {
  var now = new Date();
  this.MessageID = messageid; // Every message sent has a unique id. The response will have the same id.
  this.Method = method; // The method that was called such as startsimplegame.
  this.Result = result; // The function to call if the response arrives.
  this.Reject = reject; // The function to call if the response is an error or times out.
  this.CreateDate = now.getTime()/1000; // Used to determine if this message timed out.
}

// Start of ChatClient object definition:
var ChatClient = function() {
   this.Connection = 0;
   this.IsConnected = false;
   this.OnClose = 0; // You can set this to a function to run when the client closes.
   this.MessageHolders = [];
   this.NextMessageID = Math.floor(Math.random()*99999+1);
   this.ChatClientID = "0";
   // Events:
   this.OnClose = 0; // You can set this to a function to run when the client closes.
   this.SimpleGameJoined = 0; // You can set this to a function to run when a user joins a game you're in.
   this.SimpleGameLeft = 0; // You can set this to a function to run when a user leaves a game you're in.
   this.SimpleGameReceivedChat = 0; // You can set this to a function to run when a chat message arrives from a game you're in.
   this.SimpleGameDataChanged = 0; // You can set this to a function to run when a simple game has a data change.
   this.SimpleGameUsers = 0; // You can set this to a function to run when a simple game changes the list of users.   
   this.SimpleGameUserData = 0; // You can set this to a function to run when a simple game user data changes.
}

ChatClient.prototype.RejectOldMessages = function(ageInSeconds)
{
  var count = this.MessageHolders.length;
  var loop;
  var message;
  var old = [];
  if (count == 0) {
    return;
  }
  var now = new Date();
  var expired = now.getTime() / 1000 - ageInSeconds;
  for(loop = count-1;loop >= 0;loop--) {
    message = this.MessageHolders[loop];
    if (message.CreateDate < expired) {
	  this.MessageHolders.splice(loop,1);
	  old.push(message);
	}
  }
  count = old.length;
  for(loop=0;loop<count;loop++) {
    message = old[loop];
	if (message.Reject) {
	  message.Reject("Message timeout.");
	}
  }
}

// Parses the parameters of a chat return data. Returns an array of parameters or an error string.
// types is the type of each parameter as i or s.
ChatClient.prototype.ParseParameters = function(parameters,types) {
  var result = [];
  var pos = 0;
  var search;
  var len;
  var loop;
  for(loop=1;loop<=types.length;loop++) {
    switch (types.substring(loop-1,loop)) {
      case "i": {
        // Integer
        search = parameters.indexOf(',',pos);
        if (search < 0) {
           if (loop == types.length) {
             len = parseInt(parameters.substring(pos,parameters.length));
             pos = parameters.length;
           } else { 
             return "parameter " + loop + " was incorrect in ("+types+")";
           }
        } else {
          len = parseInt(parameters.substring(pos,search));
          pos = search + 1;
        }
        result.push(len);
        break;
      }
      case "s": {
        // String. The string length then a comma then the string.
        search = parameters.indexOf(',',pos);
        if (search < 0) {
          return "parameter " + loop + " was incorrect in ("+types+")"; 
        }
        len = parseInt(parameters.substring(pos,search));
        pos = search + 1;
        if ((len < 0) || (pos + len > parameters.length)) {
          return "parameter " +loop + " was incorrect in ("+types+")";
        }
        result.push(parameters.substring(pos,pos+len));
        pos += len;
        break;
      }
      case "v": {
        // The rest of the parameter is a variable size list of strings.
        while(pos < parameters.length) {
          search = parameters.indexOf(',',pos);
          if (search < 0) {
            break;
          }
          len = parseInt(parameters.substring(pos,search));
          pos = search + 1;
          if ((len < 0) || (pos + len > parameters.length)) {
            return "parameter " +loop + " was incorrect in ("+types+")";
          }
          result.push(parameters.substring(pos,pos+len));
          pos += len;
        }
        break;
      }
      default: {
         return "parameter " + loop + " was incorrect in ("+types+")";
      }
    }
  }
  
  return result;
}

ChatClient.prototype.FindMessageHolderByMessageID = function(messageid) {
  var count = this.MessageHolders.length;
  var loop;
  for(loop=0;loop<count;loop++) {
    if (this.MessageHolders[loop].MessageID === messageid) {
      return loop;
    }
  }
  return -1;
}

ChatClient.prototype.FindMessageHolderByMethod = function(method) {
  var count = this.MessageHolders.length;
  var loop;
  for(loop=0;loop<count;loop++) {
    if (this.MessageHolders[loop].Method === method) {
      return loop;
    }
  }
  return -1;
}

ChatClient.prototype.ProcessMessage = function(message) {
  var start = message.indexOf("(");
  var method = "";
  var parameters;
  var parsed;
  var search;
  var index;
  var holder;
  if ((start > 0) && (message.substring(message.length-1,message.length) == ")")) {
    method = message.substring(0,start);
    parameters = message.substring(start+1,message.length-1);
  } 
  switch (method) {
    case "yourchatclientid": {
      parsed = this.ParseParameters(parameters,"i");
      if (parsed.length == 1) {
        this.ChatClientID = parsed[0];
      }
      index = this.FindMessageHolderByMethod(method);
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(parsed[0]);
      }
      break;
    }
    case "error": {
      parsed = this.ParseParameters(parameters,"si");
      if ((typeof parsed === 'string') || (parsed instanceof String)) {
        alert("Error:" + parsed+"\nmessage="+message);
      } else {
        if (parsed[1] != '0') {
          index = this.FindMessageHolderByMessageID(parsed[1]);
          if (index >= 0) {
            holder = this.MessageHolders[index];
            this.MessageHolders.splice(index,1);
            holder.Reject(parsed[0]);
            break;
          }
        }
        // This is a programming error.
        alert("Error:Error message =\n"+parsed[0]);
      }
      break;
    }
	case "success": {
      parsed = this.ParseParameters(parameters,"i");
      index = this.FindMessageHolderByMessageID(parsed[0]);
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(0);
      }
      break;
    }
    case "simplegameleave": {
      parsed = this.ParseParameters(parameters,"isss");
      index = this.FindMessageHolderByMessageID(parsed[0]);
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(parsed);
        break;
      }
      if (this.SimpleGameLeft !== 0) {
        this.SimpleGameLeft(parsed);
      }
      break;
    }
    case "simplegamestarted": {
      parsed = this.ParseParameters(parameters,"is");
      index = this.FindMessageHolderByMessageID(parsed[0]);
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(parsed[1]);
      }
      break;
    }
    case "simplegamejoined": {
      parsed = this.ParseParameters(parameters,"issv");
      index = this.FindMessageHolderByMessageID(parsed[0]);
      if (index < 0) {
        index = this.FindMessageHolderByMethod(method);
      }
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(parsed); // parsed = [messageid,gameid,gamedata,user list]
        break;
      }
      if (this.SimpleGameJoined !== 0) {
        this.SimpleGameJoined(parsed);
      }
      break;
    }
    case "simplegamedata": {
      parsed = this.ParseParameters(parameters,"isss");
      index = this.FindMessageHolderByMessageID(parsed[0]);
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(parsed);
        break;
      }
      if (this.SimpleGameDataChanged !== 0) {
        this.SimpleGameDataChanged(parsed);
      }
      break;
    }
	case "simplegameusers": {
		parsed = this.ParseParameters(parameters,"isv");
		if (parsed[0] != 0) {
			index = this.FindMessageHolderByMessageID(parsed[0]);
			if (index >= 0) {
				holder = this.MessageHolders[index];
				this.MessageHolders.splice(index,1);
				holder.Result(parsed);
				break;
			}
		}
		if (this.SimpleGameUsers !== 0) {
			this.SimpleGameUsers(parsed);
		}
		break;
	}
	case "simplegamechat": {
      parsed = this.ParseParameters(parameters,"isss");
      index = this.FindMessageHolderByMessageID(parsed[0]);
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(parsed);
        break;
      }
      if (this.SimpleGameReceivedChat !== 0) {
        this.SimpleGameReceivedChat(parsed);
      }
      break;
	}
	case "simplegameuserdata": {
	  parsed = this.ParseParameters(parameters,"isss");
	  index = this.FindMessageHolderByMessageID(parsed[0]);
      if (index >= 0) {
        holder = this.MessageHolders[index];
        this.MessageHolders.splice(index,1);
        holder.Result(parsed);
        break;
      }
	  if (this.SimpleGameUserData !== 0) {
	    this.SimpleGameUserData(parsed);
	  }
	  break;
	}
    default: {
      alert("Error 2:" + message);
      break;
    }
  }
}

// Connect to the server.
ChatClient.prototype.Connect = function() {
  var that = this;
  that.IsConnected = false;
  var promise = new Promise(function(result,reject) {
    try {
      if ((that.Connection !== 0) && (that.Connection.readyState === WebSocket.OPEN)) {
        if (result) {
          result();
        }
        return;
      }	  
	  that.Connection = new WebSocket((document.URL.toLowerCase().substring(0,5)=="https" ? 'wss' : 'ws') +'://' + document.domain + ':' + location.port + '/websocket/','chatroom-protocol');
	  that.Connection.onopen = function() {
        that.IsConnected = true;
        if (result) {
          result();
        }
      }
      that.Connection.onerror = function(e) {
        that.IsConnected = false;
        if (reject) {
          reject("Error connecting to the server.");
        }
      }
      that.Connection.onclose = function() {
        that.IsConnected = false;
        if (that.OnClose !== 0) {
          that.OnClose();
        }
      }
      that.Connection.onmessage = function(e) {
        that.ProcessMessage(e.data);
      }
    } catch(err) {
      if (reject) {
        reject(err);
      }
    }
  });
  return promise;
}

// Disconnect from the server.
ChatClient.prototype.Disconnect = function() {
  if ((this.Connection !== 0) && (this.Connection.readyState === WebSocket.OPEN)) {
    this.Connection.close();
  } else {
    if (this.OnClose !== 0) {
      this.OnClose();
    }
  }
}

ChatClient.prototype.StartSimpleGame = function(gameid,player1name,gamedata) {
  var that = this;
  var promise = new Promise(function(result,reject) {
    var messageholder = new MessageHolder(that.NextMessageID++,"startsimplegame",result,reject);
    that.MessageHolders.push(messageholder);
    var message = "startsimplegame("+messageholder.MessageID+","+gameid.length+","+gameid+player1name.length+","+player1name+gamedata.length+","+gamedata+")";
    that.Connection.send(message);
  });
  return promise;
}

ChatClient.prototype.JoinSimpleGame = function(gameid,username,gamedata) {
  var that = this;
  var promise = new Promise(function(result,reject) {
    var messageholder = new MessageHolder(that.NextMessageID++,"simplegamejoined",result,reject);
    that.MessageHolders.push(messageholder);
    var message = "joinsimplegame("+messageholder.MessageID+","+gameid.length+","+gameid+username.length+","+username+gamedata.length+","+gamedata+")";
    that.Connection.send(message);
  });
  return promise;
}

ChatClient.prototype.SimpleGameChat = function(gameid,username,output_message) {
	var that = this;
	var promise = new Promise(function(result,reject) {
    var messageholder = new MessageHolder(that.NextMessageID++,"simplegamechat",result,reject);
    that.MessageHolders.push(messageholder);
    var message = "simplegamechat("+messageholder.MessageID+","+gameid.length+","+gameid+username.length+","+username+output_message.length+","+output_message+")";
    that.Connection.send(message);
  });
  return promise;
}

ChatClient.prototype.SimpleGameSetData = function(gameid,offset,newgamedata) {
  var that = this;
  var promise = new Promise(function(result,reject) {
    var messageholder = new MessageHolder(that.NextMessageID++,"simplegamedata",result,reject);
    that.MessageHolders.push(messageholder);
    var message = "simplegamesetdata("+messageholder.MessageID+","+gameid.length+","+gameid+offset+","+newgamedata.length+","+newgamedata+")";
    that.Connection.send(message);
  });
  return promise;
}

ChatClient.prototype.SimpleGameGetUsers = function(gameid) { // get the list of users in a game.
	var that = this;
	var promise = new Promise(function(result,reject) {
		var messageholder = new MessageHolder(that.NextMessageID++,"simplegameusers",result,reject);
		that.MessageHolders.push(messageholder);
		var message = "simplegameusers("+messageholder.MessageID+","+gameid.length+","+gameid+")";
		that.Connection.send(message);
	});
	return promise;
}

ChatClient.prototype.LeaveSimpleGame = function(gameid,offset,newgamedata) {
  var that = this;
  var promise = new Promise(function(result,reject) {
    var messageholder = new MessageHolder(that.NextMessageID++,"simplegameleave",result,reject);
    that.MessageHolders.push(messageholder);
    var message = "leavesimplegame("+messageholder.MessageID+","+gameid.length+","+gameid+offset+","+newgamedata.length+","+newgamedata+")";
    that.Connection.send(message);
  });
  return promise;
}

ChatClient.prototype.SimpleGameUserSetData = function(gameid,username,userdata) {
  var that = this;
  var promise = new Promise(function(result,reject) {
    var messageholder = new MessageHolder(that.NextMessageID++,"success",result,reject);
	that.MessageHolders.push(messageholder);
	var message = "simplegameusersetdata("+messageholder.MessageID+","+gameid.length+","+gameid+username.length+","+username+userdata.length+","+userdata+")";
	that.Connection.send(message);
  });
  return promise;
}

ChatClient.prototype.SimpleGameUserGetData = function(gameid,username) {
  var that = this;
  var promise = new Promise(function(result,reject) {
    var messageholder = new MessageHolder(that.NextMessageID++,"simplegameuserdata",result,reject);
	that.MessageHolders.push(messageholder);
	var message = "simplegameusergetdata("+messageholder.MessageID+","+gameid.length+","+gameid+username.length+","+username+")";
	that.Connection.send(message);
  });
  return promise;
}

ChatClient.prototype.Nop = function() {
  this.Connection.send("nop()");
}

// Start of TicTacToe object

var TicTacToe = function() {
  this.Client = 0;
  this.GameID = 0;
  this.Winner = "None";
  this.KeepAlive();
  this.RejectOldMessages();
  this.GameType = 0;
  this.Users = []; // An array of username,Up/Down
  this.UserData = []; // An array of username/userdata.
};

TicTacToe.prototype.RejectOldMessages = function() {
  var that = this;
  if (this.Client !== 0) {
    this.Client.RejectOldMessages(10);
  }
  setTimeout(function() {
    that.RejectOldMessages();
  },2000);
}

TicTacToe.prototype.KeepAlive = function() {
	var that = this;
	if ((this.Client !== 0) && (this.Client.IsConnected)) {
		this.Client.Nop();
	}
	setTimeout(function() {
		that.KeepAlive();
	},20000);
}

TicTacToe.prototype.CreateGameID = function() {
  this.GameID = Math.floor(Math.random() * 999999).toString();
}

TicTacToe.prototype.GetSavedUserName = function() {
  var username = window.localStorage.getItem("UserName");
  return username === null ? "" : username;
}

TicTacToe.prototype.GetUserData = function() {
  var userdata = window.localStorage.getItem("UserData");
  return userdata === null ? "" : userdata;
}

TicTacToe.prototype.SetUserData = function(userdata) {
  var that = this;
  if (window.localStorage.getItem("UserData") !== userdata) {
    window.localStorage.setItem("UserData",userdata);
	if ((this.Client !== 0) && (this.Client.IsConnected)) {
	  var promise = this.Client.SimpleGameUserSetData(this.GameID,this.MyName,userdata);
	  promise.then(function(result) {
	    var count = that.UserData.length;
		var loop;
		for(loop=0;loop<count;loop+=2) {
		  if (that.UserData[loop] === that.MyName) {
		    that.UserData[loop+1] = userdata;
			loop = -1;
		    break;
		  }
		}
		if (loop >= 0) {
		  that.UserData.push(that.MyName);
		  that.UserData.push(userdata);
		}
	    that.DisplayUsers();
	  },function(errorMessage) {
	    alert("Error 11: "+errorMessage);
	  });
	}
  }
}

TicTacToe.prototype.SaveUserName = function(username) {
  window.localStorage.setItem("UserName", username);
}

TicTacToe.prototype.NewGame = function() {
  this.CreateGameID();
  $('#divGameStart').hide();
  $('#divPlayer1AskName').show();
  $('#Player1Name').val(this.GetSavedUserName());
  if (this.GameType === 0) {
    this.SetGameTypeDropDown('GameTypeID');
  }
}

TicTacToe.prototype.GetUserIndex = function(username) {
  var count = this.Users.length;
  var loop;
  for(loop=0;loop<count;loop+=2) {
    if (this.Users[loop] === username) {
	  return loop;
	}
  }
  return -1;
}

TicTacToe.prototype.OnClose = function() {
  var that = this;
  var userindex = this.GetUserIndex(this.MyName);
  if (userindex >= 0) {
    this.Users[userindex+1] = "Down";	
	this.DisplayUsers();
	if (this.GameID !== 0) {
	  // Try to reconnect and join the game.
	  var promise = this.ReconnectGame();
	  promise.then(function(result) {
	    that.Users[userindex+1] = "Up";
	    that.DisplayUsers();
	  },function(errorMessage) {
	  
	  });
	}	
  }
}

TicTacToe.prototype.SimpleGameDataChanged = function(parameters) {
  var gameID = parameters[1];
  if (gameID !== this.GameID) {
    return;
  }
  this.GameData = parameters[3];
  var gameTypeID = this.GameData.substring(2,3);
  if (gameTypeID !== this.GameType.GameTypeID) {
    this.GameType = this.GetGameType(gameTypeID);
  }
  this.Winner = this.WhoWon();
  this.ShowBoard();
}

TicTacToe.prototype.FigureOutMySymbolBasedOnOrderInTheGame = function() {
  var userindex = this.GetUserIndex(this.MyName);
  switch (userindex) {
	case 0: {
	  this.MySymbol = this.GameData.substring(0,1);
	  break;
	}
	case 2: {
	  this.MySymbol = this.Opposite(this.GameData.substring(0,1));	   		   
	  break;		  
	}
	default:
	{
	  this.MySymbol = undefined;
	  break;
	}
  }
}

TicTacToe.prototype.SimpleGameUserData = function(parsed) {
  var count;
  var loop;
  if (parsed[1] === this.GameID) {
    count = this.UserData.length;
    for(loop=0;loop<count;loop+=2) {
      if (this.UserData[loop] === parsed[2]) {
	    this.UserData[loop+1] = parsed[3];
		loop = -1;
		break;
	  }
    }
	if (loop >= 0) {
	  this.UserData.push(parsed[2]);
	  this.UserData.push(parsed[3]);
	}
	this.DisplayUsers();
  }
}

TicTacToe.prototype.SimpleGameLeft = function(parsed) {
  // A simplegameleave message was received.
  if (parsed[1] != this.GameID) {
    return;
  }
  this.GameData = parsed[3];
  var count = this.Users.length;
  var loop;
  var user = parsed[2];
  for(loop=0;loop<count;loop+=2) {
    if (this.Users[loop] == user) {
      this.Users.splice(loop,2);	  
	  this.DisplayUsers();
	  break;
	}
  }
  this.FigureOutMySymbolBasedOnOrderInTheGame();
  this.ShowBoard();
  alert(user + " left the game.");
}

TicTacToe.prototype.CreateClient = function() {
	var that = this;
	this.Client = new ChatClient();
	// Add event hooks.
	this.Client.SimpleGameDataChanged = function(parsed) {
		that.SimpleGameDataChanged(parsed);
	};
	this.Client.SimpleGameLeft = function(parsed) {
		that.SimpleGameLeft(parsed);
	};
	
	this.Client.SimpleGameReceivedChat = function(parsed) {
		that.SimpleGameReceivedChat(parsed);
	}

	this.Client.SimpleGameUsers = function(parsed) {
		that.SimpleGameUsers(parsed);
	}
	
	this.Client.SimpleGameJoined = function(parsed) {
		that.SimpleGameJoined(parsed);
	}
	
	this.Client.SimpleGameUserData = function(parsed) {
	  that.SimpleGameUserData(parsed);
	}
	
	this.Client.OnClose = function() {
		that.OnClose();
	}
}

TicTacToe.prototype.SimpleGameUsers = function(parameters) {
  if (parameters[1] == this.GameID) {
    parameters.splice(0,2);
    this.Users = parameters;
    this.DisplayUsers();
  }
}

TicTacToe.prototype.DisplayUsers = function() {
	var count = this.Users.length;
	var loop;
	var divUsers = $('#divUsers');
	var output = [];
	var userindex;
	var userdataCount = this.UserData.length;
	var userdataLoop;
	output.push("<table border='1'><tr><td colspan='4' align='center'>Who's Playing</td></tr><tr><td>User</td><td>Symbol</td><td>Connection</td><td>Profile</td></tr>");
	for(loop=0;loop<count;loop+=2) {
		output.push("<tr");
		if (this.Users[loop] === this.MyName) {
		  output.push(" class='CurrentUser'");
		}
		output.push("><td>"+this.Users[loop] + "</td><td>");
		switch (loop) {
			case 0: {
					output.push(this.GameData.substring(0,1));
					break;
				}
			case 2: {
					output.push(this.Opposite(this.GameData.substring(0,1)));
					break;
				}
			default: {
				output.push("&nbsp;");
				break;
			}
		}
		if (userindex == -1) {
			output.push("&nbsp;");
		} else {
			if (userindex == loop) {
				
			} else {
			}
		}
		output.push("</td><td>"+this.Users[loop+1]+"</td>");
		output.push("<td");
		if (this.Users[loop] === this.MyName) {
		  output.push(" id='tdUserData'");
		}
		output.push(">");
		for(userdataLoop=0;userdataLoop<userdataCount;userdataLoop+=2) {
		  if (this.UserData[userdataLoop] === this.Users[loop]) {
		    output.push(this.UserData[userdataLoop+1]
			 .replace(/&/g, "&amp;")
			 .replace(/</g, "&lt;")
			 .replace(/>/g, "&gt;")
			 .replace(/"/g, "&quot;")
			 .replace(/'/g, "&#039;"));
		    break;
		  }
		}
		if (this.Users[loop] === this.MyName) {
		  output.push("<input type='button' value='Edit' onclick='ticTacToe.EditUserData()' />");
		}
		output.push("</td></tr>");
	}
	output.push("</table>");	
	divUsers.show();	
	divUsers.html(output.join(""));
}

TicTacToe.prototype.EditUserData = function() {
  var count = this.UserData.length;
  var loop;
  $('#tdUserData').html("<textarea name='UserData' id='UserData' rows='6' cols='60'></textarea><br /><input type='button' value='Save' onclick='ticTacToe.SaveUserDataChanges()' /><input type='button' value='Cancel' onclick='ticTacToe.DisplayUsers()' />");
  for(loop=0;loop<count;loop+=2) {
    if (this.UserData[loop] === this.MyName) {
	  $('#UserData').val(this.UserData[loop+1]);
	  break;
	}
  }  
}

TicTacToe.prototype.SaveUserDataChanges = function() {
  var userdata = $('#UserData').val();
  this.DisplayUsers();
  this.SetUserData(userdata);
}

TicTacToe.prototype.SimpleGameReceivedChat = function(parameters) {
	var table = $('#tblMessages')[0];
	var row;
	var cell;
	if (parameters[1] !== this.GameID) {
		return;
	}
	while (table.rows.length > 10) {
		table.deleteRow(1);
	}
	row = table.insertRow(table.rows.length-1);
	cell = row.insertCell(0);
	cell.innerHTML = parameters[2];
	cell.className = "RB";
	cell = row.insertCell(1);
	cell.innerHTML = parameters[3];
}

TicTacToe.prototype.SimpleGameJoined = function(parameters) {
	// This event is called by the server when someone joins the game.
	var that = this;
	if (parameters[1] === this.GameID) {
		this.GameData = parameters[2];
		this.GameType = this.GetGameType(this.GameData.substring(2,3));
		parameters.splice(0,3);
		this.Users = parameters;
		$('#divWaitingForPlayer2').hide();
		$('#divGameBoard').show();
		this.ShowBoard();
		this.DisplayUsers();
		// Request the user data for all the users.
		var promise = this.Client.SimpleGameUserGetData(this.GameID,"");
		promise.then(function(result) {
		
		},function(errorMessage) {
		  alert("Error 13: "+errorMessage3);
		});
	}
}

TicTacToe.prototype.GetGameType = function(gameTypeID) {
  var count = this.GameTypes.length;
  var loop;
  for(loop=0;loop<count;loop++) {
    if (this.GameTypes[loop].GameTypeID === gameTypeID) {
	  return this.GameTypes[loop];
	}
  }
  return 0;
}

TicTacToe.prototype.Spaces = function(length) {
  var output = [];
  var loop;
  for(loop=0;loop<length;loop++) {
    output.push(' ');  
  }
  return output.join('');
}

TicTacToe.prototype.WantsToBe = function(XorO) {
  // The user is creating a new game and clicks on Wants To Be X or Wants To Be O.
  var that = this;
  this.MySymbol = XorO;
  this.MyName = $('#Player1Name').val();
  if (this.MyName.length == 0) {
    alert("Please enter your name.");
    $('#Player1Name').focus();
    return;
  }
  this.SaveUserName(this.MyName);
  this.GameType = this.GetGameType($('#GameTypeID').val());
  this.GameData = this.MySymbol+this.MySymbol+this.GameType.GameTypeID+this.Spaces(this.GameType.Width * this.GameType.Height);
  $('#divPlayer1AskName').hide();
  $('#divCopntactingServer').show();
  var userdata = this.GetUserData();
  this.UserData.push(this.MyName);
  this.UserData.push(userdata);
  var StartGame = function() {
    that.Users = [that.MyName,((that.Client === 0) || (!that.Client.IsConnected)) ? "Down" : "Up"];
    $('#WaitingForPlayer2GameID').html(that.GameID);
    var promise2 = that.Client.StartSimpleGame(that.GameID,that.MyName,that.GameData);
    promise2.then(function(result2) {
      $('#divWaitingForPlayer2').show();
	  $('#divUsers').show();
	  that.DisplayUsers();
	   if (userdata !== "") {
	     var promise3 = that.Client.SimpleGameUserSetData(that.GameID,that.MyName,userdata);
	     promise3.then(function(result) {
	       that.DisplayUsers();
	     },function(errorMessage) {
	       alert("Error 13: "+errorMessage);
	     });
       }
    },function(errorMessage2) {
      alert("Error 3:" + errorMessage2);
      $('#divWaitingForPlayer2').hide();
      $('#divGameStart').show();
    });
  }
  if (this.Client === 0) {
	this.CreateClient();     
  }
  if (this.Client.IsConnected) {
    StartGame();
  } else {
    var promise = this.Client.Connect();
    promise.then(function(result) {
      StartGame();
    },function(errorMessage) {
      alert("Error 4:" + errorMessage);
      $('#divContactingServer').hide();
      $('#divGameStart').show();
    });
  }
}

TicTacToe.prototype.ReconnectGame = function() {
  // When the connection fails during a game, reconnect, then rejoin the game.
  var that = this;
  var promise = new Promise(function(result,reject) {
    var promise2 = that.Client.Connect();
	promise2.then(function(result2) {
	  var promise3 = that.Client.JoinSimpleGame(that.GameID,that.MyName,"");
	  promise3.then(function(result3) {
	    if (result) {
		  result(result3);
		}
	  },function(errorMessage3) {
	    if (reject) {
		  reject(errorMessage3);
		}
	  });
	},function(errorMessage2) {
	  if (reject) {
	    reject(errorMessage2);
	  }
	});
  });
  return promise;
}

TicTacToe.prototype.Choose = function(spot) {
  // This is called when the player clicks on an empty square.
  // spot is between 0 and this.GameType.Width * this.GameType.Height - 1
  var that = this;
  var oldGameData;
  var yourTurn = this.Opposite(this.MySymbol);
  oldGameData = this.GameData;
  this.GameData = this.GameData.substring(0,1) + yourTurn + this.GameType.GameTypeID + this.GameData.substring(3,spot+3)+this.MySymbol+this.GameData.substring(spot+4,this.GameData.length);
  this.Winner = this.WhoWon();
  this.ShowBoard();
  var ReverseMove = function() {
    that.GameData = oldGameData;
	that.Winner = that.WhoWon();
	that.ShowBoard();
  }
  var SetData = function() {
    var promise = that.Client.SimpleGameSetData(that.GameID,0,that.GameData);
    promise.then(function(result) {
      that.Winner = that.WhoWon();
      that.ShowBoard();
    },function(errorMessage) {
      alert("Error 5:" + errorMessage);
	  ReverseMove();
    });
  }
  if (this.Client.IsConnected) {
    SetData();
  } else {
    var promise2 = this.ReconnectGame();
	promise2.then(function(result2) {
	  SetData();
	},function(errorMessage2) {
	  alert("Error 8:"+errorMessage2);
	  ReverseMove();
	});
  }
}

TicTacToe.prototype.Opposite = function(x) {
  return x === "X" ? "O" : "X";
}

TicTacToe.prototype.PlayAgain = function() {
	// The user chose the Play Again button.
	var that = this;
	this.GameType = this.GetGameType($('#GameTypeIDAgain').val());
	this.GameData = this.GameData.substring(0,1) + this.Opposite(this.MySymbol) + this.GameType.GameTypeID + this.Spaces(this.GameType.Width * this.GameType.Height); // The other guy goes first.
	this.Winner = "None";
	var ResetGame = function() {
	  var promise = that.Client.SimpleGameSetData(that.GameID,0,that.GameData);
	  $('#divContactingServer').show();
	  $('#divGameBoard').hide();
	  promise.then(function(result) {
		  $('#divContactingServer').hide();
		  $('#divGameBoard').show();
		  that.ShowBoard();
	  },function(errorMessage) {
		  $('#divContactingServer').hide();
		  alert(errorMessage);
		  $('#divGameStart').show();
	  });
	}
	if (this.Client.IsConnected) {
	  ResetGame();
	} else {
	  var promise2 = this.ReconnectGame();
	  promise2.then(function(result2) {
	    ResetGame();
	  },function(errorMessage2) {
	    
	  });	  
	}
}

TicTacToe.prototype.WhoWon = function() {
  // returns X or O or Tie or None
  var x;
  var y;
  var z;
  var wasTie;
  var result;
  var gamedata;
  var lineLength = this.GameType.LineLength;
  var width = this.GameType.Width;
  var height = this.GameType.Height;
  var first;
  // Fill gamedata array.
  gamedata = [];
  for(x=0;x<width*height;x++) {
    gamedata.push(this.GameData.substring(x+3,x+4));
  }
  // Look for going across.
  for(x=0;x<=width-lineLength;x++) {    
    for(y=0;y<height;y++) {
	  first = gamedata[y * height + x];
	  if (first === ' ') {
	    continue;
	  }
	  for(z=1;z<lineLength;z++) {
	    if (gamedata[y * height + x + z] != first) {
		  z = -1;
		  break;
		}
	  }
	  if (z != -1) {
	    return first;
	  }
	}
  }
  // Look for going down.
  for(x=0;x<width;x++) {    
    for(y=0;y<=height-lineLength;y++) {
	  first = gamedata[y * height + x];
	  if (first === ' ') {
	    continue;
	  }
	  for(z=1;z<lineLength;z++) {
	    if (gamedata[(y + z) * height + x] != first) {
		  z = -1;
		  break;
		}
	  }
	  if (z != -1) {
	    return first;
	  }
	}
  }
  // Look diagonally.
  for(x=0;x<=width-lineLength;x++) {    
    for(y=0;y<=height-lineLength;y++) {
	  first = gamedata[y * height + x];
	  if (first !== ' ') {
	    for(z=1;z<lineLength;z++) {
	      if (gamedata[(y + z) * height + x + z] != first) {
		    z = -1;
		    break;
		  }
	    }
	    if (z != -1) {
	      return first;
	    }
	  }
	  // Diagonally the other way.
	  first = gamedata[y * height + x + lineLength - 1];
	  if (first !== ' ') {
	    for(z=1;z<lineLength;z++) {
	      if (gamedata[(y + z) * height + x + (lineLength - 1 - z)] != first) {
		    z = -1;
		    break;
		  }
	    }
	    if (z != -1) {
	      return first;
	    }
	  }
	}
  }
  // Look for a tie.
  wasTie = true;
  for(x=width*height-1;x>=0;x--) {
    if (gamedata[x] === ' ') {
	  wasTie = false;
	}
  }
  return wasTie ? "Tie" : "None";
}

TicTacToe.prototype.JoinGameAskGameID = function() {
  $('#divGameStart').hide();
  $('#divJoinGameAskName').show();
  $('#JoinGameID').val('');
  $('#Player2Name').val(this.GetSavedUserName());
}

TicTacToe.prototype.MainMenu = function() {
  $('#divJoinGameAskName').hide();
  $('#divPlayer1AskName').hide();
  if ((this.GameID === 0) || (this.Client === 0) || (!this.Client.IsConnected)) {
    $('#divGameStart').show();
  } else {
    $('#divContactingServer').show();
    var promise = this.Client.LeaveSimpleGame(this.GameID,0,"");
    promise.then(function(result) {
      $('#divContactingServer').hide();
      $('#divGameStart').show();
    },function(errorMessage) {
      alert("Error leaving the game:"+errorMessage);
      $('#divContactingServer').hide();
      $('#divGameStart').show();
    });
  }
}

TicTacToe.prototype.JoinGame = function() {
  var that = this;
  this.MyName = $('#Player2Name').val();
  this.GameID = $('#JoinGameID').val();
  if (this.MyName.length == 0) {
    alert("Please enter your name.");
    $('#Player2Name').focus();
    return;
  }  
  if (this.GameID.length == 0) {
    alert("Please enter the GameID you got from the other player.");
    $('#JoinGameID').focus();
    return;
  }
  this.SaveUserName(this.MyName);
  var userdata = this.GetUserData();
  this.UserData.push(this.MyName);
  this.UserData.push(userdata);  
  $('#divJoinGameAskName').hide();
  $('#divContactingServer').show();
  if (this.Client === 0) {
	this.CreateClient();
  }
  var promise2 = this.Client.Connect();
  promise2.then(function(result2) {
    var promise = that.Client.JoinSimpleGame(that.GameID,that.MyName,"");
    promise.then(function(result) {
       $('#divContactingServer').hide();
       $('#divGameBoard').show();
       that.GameData = result[2];
	   that.GameType = that.GetGameType(that.GameData.substring(2,3));
	   result.splice(0,3);
	   that.Users = result;
	   that.FigureOutMySymbolBasedOnOrderInTheGame();
	   that.Winner = that.WhoWon();
	   that.DisplayUsers();
       that.ShowBoard();
	   var userdata = that.GetUserData();
	   if (userdata !== "") {
	     var promise2 = that.Client.SimpleGameUserSetData(that.GameID,that.MyName,userdata);
	     promise2.then(function(result) {
	       that.DisplayUsers();
	     },function(errorMessage) {
	       alert("Error 12: "+errorMessage);
	     });
       }
	   // Request the user data for all the users.
		var promise3 = that.Client.SimpleGameUserGetData(that.GameID,"");
		promise3.then(function(result3) {
		
		},function(errorMessage3) {
		  alert("Error 13: "+errorMessage3);
		});
    },function(errorMessage) {
	  alert("Error 6:" + errorMessage);
	  $('#divJoinGameAskName').show();
	  $('#divContactingServer').hide();
    });
  },function(reject2) {
  });
}

TicTacToe.prototype.LeaveTheGame = function() {
  // The user clicked the Leave The Game button.
  var that = this;
  if (this.Client === 0) {
    alert("You're not connected.");
	return;
  }
  if (this.Users[0] == this.MyName) {
    // If this player is the first player in the list, reverse the first character to show that the second player, who will now be the first player, switched places.
    this.GameData = this.Opposite(this.GameData.substring(0,1)) + this.GameData.substring(1,this.GameData.length);
  }
  var promise = this.Client.LeaveSimpleGame(this.GameID,0,this.GameData);
  promise.then(function(result) {    
    that.SimpleGameLeft(result);
	$('#divGameStart').show();
	$('#divGameBoard').hide();
	$('#divUsers').hide();
	that.GameID = 0;
	that.Client.OnClose = function() {
	  that.Client = 0;
	}
	that.Client.Disconnect();
  },function(errorMessage) {
    alert("Error 10:"+errorMessage);
  });
}

TicTacToe.prototype.SendMessage = function() {
	var that = this;
	var message;
	if (this.Client === 0) {
		alert("You're not connected.");
		return;
	}
	message = $('#NewMessage').val();
	if (message == "") {
		return;
	}
	var SendTheMessage = function() {
	  $('#btnSendMessage').attr('disabled',true);
	  var promise = that.Client.SimpleGameChat(that.GameID,"",message);
	  promise.then(function(result) {
		$('#btnSendMessage').attr('disabled',false);
		that.SimpleGameReceivedChat(result);
	  	$('#NewMessage').val('');
	  },function(errorMessage) {
		$('#btnSendMessage').attr('disabled',false);
		alert("Error 7: " + errorMessage);
	  });
	}
	if (this.Client.IsConnected) {
	  SendTheMessage();
	} else {
	  var promise2 = this.ReconnectGame();
	  promise2.then(function(result2) {
	    SendTheMessage();
	  },function(errorMessage2) {
	    alert("Error 9:"+errorMessage2);
	  });
	}	
}

TicTacToe.prototype.BeforePageUnload = function(event) {
  if ((this.Client !== 0) && (this.Client.IsConnected) && (this.GameID !== 0)) {
    this.LeaveTheGame();
  }
}

TicTacToe.prototype.GameTypes = [
	{
		"GameTypeID":"1",
		"GameName":"3 in a row (standard Tic Tac Toe)",
		"Width":3,
		"Height":3,
		"LineLength":3
	},
	{
		"GameTypeID":"2",
		"GameName":"4 in a row (7 x 7 grid)",
		"Width":7,
		"Height":7,
		"LineLength":4		
	},
	{
		"GameTypeID":"3",
		"GameName":"5 in a row (11 x 11 grid)",
		"Width":11,
		"Height":11,
		"LineLength":5
	},
	{
		"GameTypeID":"4",
		"GameName":"5 in a row (30 x 30 grid)",
		"Width":30,
		"Height":30,
		"LineLength":5
	}
];

TicTacToe.prototype.BuildGameBoard = function(table,width,height)
{
  var x;
  var y;
  var row;
  var cell;
  var classNames;
  var width = this.GameType.Width;
  var height = this.GameType.Height;
  while(table.rows.length > 0) {
    table.deleteRow(0);
  }
  for(y=0;y<height;y++) {
    row = table.insertRow(-1);
	for(x=0;x<width;x++) {
	  cell = row.insertCell(-1);
	  cell.innerHTML = " ";
	  // Create the class name.
	  classNames = [];
	  if (x > 0) {
	    classNames.push("LB");
	  }
	  if (x < width-1) {
	    classNames.push("RB");
	  }
	  if (y > 0) {
	    classNames.push("TB");
	  }
	  if (y < height-1) {
	    classNames.push("BB");
	  }
	  cell.className = classNames.join(" ");
	}
  }
  row = table.insertRow(-1);
  cell = row.insertCell(-1);
  cell.colSpan = width;
  cell.style.textAlign = "center";
}

TicTacToe.prototype.SetGameTypeDropDown = function(dropDownID) {
	var dropdown = document.getElementById(dropDownID);
	var count = this.GameTypes.length;
	var loop;
	var option;
	for(loop=0;loop<count;loop++) {
		var option = document.createElement('option');
		option.text = this.GameTypes[loop].GameName;
		option.value = this.GameTypes[loop].GameTypeID;
		dropdown.options[loop] = option;
	}
}

TicTacToe.prototype.ShowBoard = function() {
  var myTurn = (this.MySymbol === this.GameData.substring(1,2));
  var table = document.getElementById('GameTable');
  var loop;
  var row;
  var cell;
  var stillPlaying = (this.Winner == "None");
  var width = this.GameType.Width;
  var height = this.GameType.Height;
  var select;
  $('#spanGameBoardGameID').html(this.GameID);
  $('#spanGameType').html(this.GameType.GameName);
  if ((table.rows.length != height+1) || (table.rows[0].cells.length != width)) {
    this.BuildGameBoard(table,width,height);
  }
  for(loop=0;loop<width*height;loop++) {
    row = table.rows[Math.floor(loop/height)];
    cell = row.cells[loop%height];
    switch(this.GameData.substring(loop+3,loop+4))
    {
       case 'X': {
         cell.innerHTML = "X";
         break;
       }
       case 'O': {
         cell.innerHTML = "O";
         break;
       }
       default: {
         if (myTurn && stillPlaying) {
           cell.innerHTML = "<input type='button' value=' ' onclick='ticTacToe.Choose("+loop+")' />";
         } else {
           cell.innerHTML = "&nbsp;&nbsp;";
         }
       }
    }
  }
  row = table.rows[height];
  cell = row.cells[0];
  if (stillPlaying) {
	cell.innerHTML = (myTurn ? "It's your turn. You're " + this.MySymbol + ".<br />Click on an empty square." : "It's their turn. You're " + (this.MySymbol === undefined ? "not playing" : this.MySymbol)) + ". <input type='button' value='Leave The Game' onclick='ticTacToe.LeaveTheGame()' />";
  } else {
    select = [];
	select.push("<select name='GameTypeIDAgain' id='GameTypeIDAgain'>");
	for(loop=0;loop<this.GameTypes.length;loop++) {
		select.push("<option value='" + this.GameTypes[loop].GameTypeID + "'");
		if (this.GameTypes[loop].GameTypeID === this.GameType.GameTypeID) {
		  select.push(" selected");
		}
		select.push(">"+this.GameTypes[loop].GameName + "</option>");
	}
	select.push("</select>");
	cell.innerHTML = (this.Winner == "Tie" ? "The game was a tie." : this.Winner + " won.") + "<br />Do you want to <input type='button' value='Play Again ?' onclick='ticTacToe.PlayAgain()' /><br />" + select.join("");
  }
}

var ticTacToe = new TicTacToe();

window.addEventListener('beforeunload', (event) => {
  // If the page unloads, try to have them leave the game.
  ticTacToe.BeforePageUnload(event);
});

</script>
</head>
<body>
<center><h1>Tic Tac Toe</h1></center>
<form name='GameForm'>
<div id='divGameStart'>
This game was written to test a websocket server I wrote in C++ in Linux.<br />
I'm hoping to get a job using C/C++ in Windows or Linux.<br />
I didn't put a lot of work on the client side look because it's supposed to be a simple web socket example.<br />
<br />
Click <a href='websocket.zip' target='_blank'>here</a> to download the source
code to the web socket server.<br />
<input type='button' value='Create a new game' onclick='ticTacToe.NewGame()' />
<input type='button' value='Join an existing game' onclick='ticTacToe.JoinGameAskGameID()' />
</div>

<div id='divPlayer1AskName' class='Hidden'>
<table>
<tr><td>What is your name ?</td><td><input type='text' name='YourName' id='Player1Name' /></td></tr>
<tr><td>What game do you want ?</td><td><select name='GameTypeID' id='GameTypeID'></select></td></tr>
<tr><td>Do you want to be</td><td><input type='button' value='X' onclick='ticTacToe.WantsToBe("X")' /> or <input type='button' value='O' onclick='ticTacToe.WantsToBe("O")' /> ? </td></tr>
<tr><td>Or go back to the</td><td><input type='button' value='Main Menu' onclick='ticTacToe.MainMenu()' /></td></tr>
</table>

</div>

<div id='divJoinGameAskName' class='Hidden'>
<table>
<tr><td colspan='2'>To join an existing game, ask the person who</td></tr>
<tr><td colspan='2'>started the game for the GameID and</td></tr>
<tr><td>enter it here:</td><td><input type='text' name='JoinGameID' id='JoinGameID' /></td></tr>
<tr><td>Enter your name:</td><td><input type='text' name='Player2Name' id='Player2Name' /></td></tr>
<tr><td colspan='2'>Click <input type='button' value='Here' onclick='ticTacToe.JoinGame()' /> to join the game.</td></tr>
<tr><td colspan='2'>Or go back to the <input type='button' value='Main Menu' onclick='ticTacToe.MainMenu()' /></td></tr>
</table>
</div>

<div id='divWaitingForPlayer2' class='Hidden'>
Waiting for player 2 to join. Please give this<br />
GameID so they can join the game: <span id='WaitingForPlayer2GameID'></span><br />
<br />
Or give up and go back to the <input type='button' value='Main Menu' onclick='ticTacToe.MainMenu()' />
</div>

<div id='divWaitingForOtherPlayer' class='Hidden'>
Waiting for the other player. . . 
</div>

<div id='divUsers' class='Hidden'>

</div>

<div id='divGameBoard' class='Hidden'>
GameID: <span id='spanGameBoardGameID'></span><br />
Game Type: <span id='spanGameType'></span><br />
<table id='GameTable'>
<tr>
  <td class='RB BB'>X</td>
  <td class='LB BB RB'>X</td>
  <td class='LB BB'>X</td>
</tr>
<tr>
  <td class='RB TB BB'>O</td>
  <td class='LB RB TB BB'>O</td>
  <td class='LB TB BB'>O</td>
</tr>
<tr>
  <td class='RB TB'>X</td>
  <td class='LB TB RB'>&nbsp;</td>
  <td class='LB TB'>X</td>
</tr>
<tr>
  <td colspan='3' align='center'></td>
</tr>
</table>
<br />
<div style='border:1px solid black;display:inline-block'>
<table border='0' id='tblMessages'>
<tr><td align='center' colspan='2'>Messages</td></tr>
<tr><td colspan='2'><input type='text' name='NewMessage' id='NewMessage' size='30' /><input type='button' value='Send' id='btnSendMessage' onclick='ticTacToe.SendMessage()' /></td>
</table>
</div>
</div>

<div id='divContactingServer' class='Hidden'>
Contacing the game server. Please wait.
</div>
</form>
</body>
</html>
